/*
 * Path.java
 * 
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.fpdev.core.basenet;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import org.fpdev.core.FPEngine;
import org.fpdev.apps.rtemaster.GUIOutputAcceptor;

import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.fpdev.apps.rtemaster.gui.map.MapCanvas;
import org.fpdev.apps.rtemaster.gui.map.MapDrawable;

public class Path implements MapDrawable {

  protected List<BLink> path_; // Sequence path_;
  protected BNode firstFNode_;
  protected GUIOutputAcceptor guiOA_;
  protected FPEngine engine_;
  protected BaseNetwork baseNet_;
  private int defaultDrawWidth_;
  private boolean defaultDrawArrows_;
  private Color defaultDrawColor_;

  public Path(FPEngine engine) {
    guiOA_ = engine.getGUIOutputAcceptor();
    engine_ = engine;
    baseNet_ = engine.getBaseNet();
    path_ = new ArrayList<BLink>();
    firstFNode_ = null;
    defaultDrawWidth_ = 3;
    defaultDrawArrows_ = false;
    defaultDrawColor_ = Color.GREEN;
  }

  public boolean isTransitPath() {
    return false;
  }

  public boolean isEmpty() {
    return path_.isEmpty();
  }

  public void updateList(List<BLink> list) {
    path_ = list;
    if (list.size() == 1) {
      firstFNode_ = list.get(0).getFNode();
    } else if (list.size() > 1) {
      BLink first = list.get(0), second = list.get(1);
      if (first.getTNode() == second.getFNode() || first.getTNode() == second.getTNode()) {
        firstFNode_ = first.getFNode();
      } else {
        firstFNode_ = first.getTNode();
      }
    }
  }

  public Iterator<BLink> getLinks() {
    return path_.iterator();
  }

  public Set<BLink> getLinkSet() {
    Set<BLink> set = new HashSet<BLink>();
    set.addAll(path_);
    return set;
  }

  public List<BLink> getLinkList() {
    List<BLink> list = new LinkedList<BLink>();
    list.addAll(path_);
    return list;
  }

  public List<BNode> getNodeList() {
    List<BNode> nodes = new LinkedList<BNode>();
    if (path_.size() > 0) {
      BNode curNode = startNode();
      nodes.add(curNode);
      Iterator<BLink> links = getLinks();
      while (links.hasNext()) {
        curNode = baseNet_.opposite(curNode, links.next());
        nodes.add(curNode);
      }
    }
    return nodes;
  }

  public Iterator<BNode> getNodes() {
    return getNodeList().iterator();
  }

  public Set<BNode> getEndPointSet() {
    Set<BNode> set = new HashSet<BNode>();
    if (path_.size() > 0) {
      set.add(startNode());
      set.add(endNode());
    }
    return set;
  }

  public int linkCount() {
    return path_.size();
  }

  public BLink getLink(int pli) {
    return path_.get(pli);
  }

  public boolean containsLink(BLink link) {
    return path_.contains(link);
  }

  public int countLinkOccurences(BLink link) {
    int count = 0;
    Iterator<BLink> links = path_.iterator();
    while (links.hasNext()) {
      if (links.next() == link) {
        count++;
      }
    }
    return count;
  }

  public int lengthFt(boolean measured) {
    int len = 0;
    Iterator<BLink> links = getLinks();
    while (links.hasNext()) {
      len += measured ? links.next().getMeasuredLength() : links.next().getLengthFeet();
    }
    return len;
  }

  public int getFirstPLI(BLink link, BNode inNode) {
    Iterator<BLink> links = getLinks();
    int i = 0;
    BNode cNode = this.startNode();
    while (links.hasNext()) {
      BLink cLink = links.next();
      if (cLink == link && inNode == cNode) {
        return i;
      }
      i++;
      cNode = baseNet_.opposite(cNode, cLink); //getGraph().opposite(cNode.getVertex(), cLink.getEdge()).element();
    }
    return -1;
  }
  
  
  /**
   * Returns all possible "In-PLIs" (the path link index of the preceding link)
   * for a specfied node.
   * 
   * TODO: exception if node not on path
   *  
   * @param node a node locating along the target path 
   * @return an iterator of Integers representing the In-PLIs for the node
   */
  public Iterator<Integer> getInPLIs(BNode node) {
    List<Integer> list = new LinkedList<Integer>();
    for (int pli = 0; pli < path_.size(); pli++) 
      if (toNode(pli) == node) list.add(new Integer(pli));
     
    return list.iterator();
  }
 
 /**
   * Returns all possible "Out-PLIs" (the path link index of the following link)
   * for a specfied node.
   * 
   * TODO: exception if node not on path
   *  
   * @param node a node locating along the target path 
   * @return an iterator of Integers representing the Out-PLIs for the node
   */
  public Iterator<Integer> getOutPLIs(BNode node) {
    List<Integer> list = new LinkedList<Integer>();
    for (int pli = 0; pli < path_.size(); pli++) 
      if (fromNode(pli) == node) list.add(new Integer(pli));
     
    return list.iterator();
  }
 
  /**
   * Creates a new path that is a subpath of the target path, given the path
   * link indices (PLIs) of the first and last links to be included in the
   * subpath. 
   * 
   * TODO: write and throw exception if endPLI < startPLI
   * 
   * @param startPLI
   * @param endPLI
   * @return the newly created sub-path
   */
  public Path subPath(int startPLI, int endPLI) {
    //System.out.println("subpath: "+startPLI+" to "+endPLI);
    Path sp = new Path(engine_);
    sp.addDirectedFirstLink(getLink(startPLI), fromNode(startPLI));
    for (int i = startPLI + 1; i <= endPLI; i++)
      sp.addToEnd(getLink(i));
    
    return sp;
  }

  /**
   * Appends a given path to the end of the target path. The target path object
   * is modified by this operation; the path provided as the "appendage" is not.
   * The end node of the target and the start node of the appendage must be
   * coincident for the operation to be successful.
   * 
   * @param path the path to be appended
   * @return true if the operation is successful, false otherwise
   */
  public boolean append(Path path) {
    if (path_.isEmpty()) {
      //System.out.println("            empty case");
      path_.addAll(path.path_);
      firstFNode_ = path.firstFNode_;
      return true;
    }

    if (path.linkCount() == 1) { // special case
      //System.out.println("            len=1 case");
      BLink link = path.path_.get(0);
      if (link.getFNode() == endNode() || link.getTNode() == endNode()) {
        //System.out.println("            returning addToEnd");
        return addToEnd(link);
      }
      if (link.getFNode() == startNode() || link.getTNode() == startNode()) {
        //System.out.println("            returning addToStart");
        return addToStart(link);
      }
      return false;
    }

    //System.out.println("            len>1 case");
    Iterator<BLink> links = new LinkedList(path.path_).iterator();
    //System.out.println("            starting loop");

    boolean success = true;
    //int i=0;
    while (links.hasNext()) {
      //System.out.println("              in loop, i="+i);
      //i++;
      boolean added = addLink(links.next());
      success = success && added;
    }
    return success;
  }

  /**
   * Removes all links from the target path.
   */
  public void clear() {
    while (!path_.isEmpty()) {
      if (path_.get(0) != null) {
        path_.get(0).unregisterPath(this);
      }
      path_.remove(0);
    }
  }

  // DRAW METHODS
  
  public void setDefaultDrawWidth(int ddw) {
    defaultDrawWidth_ = ddw;
  }

  public void setDefaultDrawArrows(boolean dda) {
    defaultDrawArrows_ = dda;
  }

  public void setDefaultDrawColor(Color ddc) {
    defaultDrawColor_ = ddc;
  }

  public void draw(MapCanvas canvas) {
    draw(canvas, defaultDrawArrows_);
  }

  public void draw(MapCanvas canvas, boolean drawArrows) {
    draw(canvas, defaultDrawColor_, defaultDrawWidth_, drawArrows);
  }

  public void draw(MapCanvas canvas, Color color, int width) {
    draw(canvas, color, width, false);
  }

  public void draw(MapCanvas canvas, Color color, int width, boolean drawArrows) {
    if (isEmpty()) {
      return;
    }

    Iterator<BLink> links = getLinks();
    canvas.getGraphics().setColor(color);
    canvas.getGraphics().setStroke(new BasicStroke(width));

    while (links.hasNext()) {
      BLink link = links.next();
      link.drawCurve(canvas);
    }

    if (drawArrows)
      this.drawArrows(canvas, color);
  }

  public void drawArrows(MapCanvas canvas, Color mainColor) {
    drawArrows(canvas, mainColor, 0, null);
  }

  public void drawArrows(MapCanvas canvas, Color mainColor, int borderWidth, Color borderColor) {
    Iterator<BLink> links = getLinks();

    BNode from = startNode(), to;
    while (links.hasNext()) {
      BLink link = links.next();
      to = baseNet_.opposite(from, link);
      //link.drawCurve(canvas, (drawArrows ? (link.getFNode()==from ? 1 : 2) : 0), 8);
      canvas.drawArrow(link, 12, link.getFNode() == to, mainColor, borderWidth, borderColor);
      from = to;
    }
  }

  public void drawTrip(MapCanvas canvas) {
    if (isEmpty()) {
      return;
    }

    //PositionIterator pathLinks = path_.positions();
    Iterator<BLink> links = getLinks();
    canvas.getGraphics().setColor(Color.green);

    //System.out.println("------------ drawing path -----------");

    BNode from = startNode(), to;
    while (links.hasNext()) {
      //Position linkPos = pathLinks.nextPosition();
      BLink link = links.next(); //(CLink) linkPos.element();
      to = baseNet_.opposite(from, link);

      link.drawCurve(canvas, (link.getFNode() == from ? 1 : 2), 8);
      from = to;
    }
  }

  @Override
  public Rectangle2D.Double getBoundingBox() {
    if (path_.size() == 0) {
      return null;
    }
    double minX = Double.MAX_VALUE, maxX = 0, minY = Double.MAX_VALUE, maxY = 0;
    Iterator<BLink> links = getLinks();
    while (links.hasNext()) {
      BLink link = links.next();
      if (link.getX1() < minX) {
        minX = link.getX1();
      }
      if (link.getX2() < minX) {
        minX = link.getX2();
      }
      if (link.getY1() < minY) {
        minY = link.getY1();
      }
      if (link.getY2() < minY) {
        minY = link.getY2();
      }
      if (link.getX1() > maxX) {
        maxX = link.getX1();
      }
      if (link.getX2() > maxX) {
        maxX = link.getX2();
      }
      if (link.getY1() > maxY) {
        maxY = link.getY1();
      }
      if (link.getY2() > maxY) {
        maxY = link.getY2();
      }
    }
    return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
  }

  public boolean visitsNode(BNode node) {
    Iterator<BLink> links = getLinks();
    while (links.hasNext()) {
      BLink link = links.next();
      if (link.getFNode() == node || link.getTNode() == node) {
        return true;
      }
    }
    return false;
  }

  public BNode fromNode(int pli) {
    //return fromNode(path_.atRank(pli));
    BNode node = firstFNode_;
    for (int i = 0; i < pli; i++) {
      node = baseNet_.opposite(node, getLink(i));
    }
    //System.out.println("RETURNING NULL FROMNODE");
    return node;
  }

  public BNode toNode(int pli) {
    //return toNode(path_.atRank(pli));
    BNode node = firstFNode_;
    for (int i = 0; i <= pli; i++) {
      node = baseNet_.opposite(node, getLink(i));
    }
    return node;
  }

  public BNode startNode() {
    if (path_.size() == 0) {
      return null;
    }
    if (startLink().getClassType() == BLink.CLASS_STREETPARTIAL) {
      return ((BLinkStreetPartial) startLink()).getAddressNode();
    }
    return fromNode(0); //path_.first());
  }

  public BNode endNode() {
    if (path_.size() == 0) {
      return null;
    }
    if (endLink().getClassType() == BLink.CLASS_STREETPARTIAL) {
      return ((BLinkStreetPartial) endLink()).getAddressNode();
    }
    return toNode(path_.size() - 1);// last());
  }

  public Point2D.Double[] getPoint(double i) {
    double len = lengthFt(true), cur = 0;
    int pli = 0;
    for (BLink link : path_) {
      double l = link.getMeasuredLength() / len;// getLengthFeetDbl();
      BNode node = this.fromNode(pli);
      
      if(cur <= i && i <= cur+l) { // this is the link
        // calculate position along link:
        double p = (i-cur) / l;
        if(link.getFNode() != node) p = 1-p;
        Point2D.Double[] ret = link.getPoint(p);
        if(fromNode(pli) != link.getFNode()) {
          ret[1].x = -ret[1].x;
          ret[1].y = -ret[1].y;
        }
        return ret;
      }
      cur += l;
      pli++;
    }
    return null;
  }
  
  public BLink startLink() {
    return path_.get(0);
  }

  public BLink endLink() {
    return path_.get(path_.size() - 1);
  }

  public BLink before(BLink link, BNode fNode) {
    if (link == startLink()) {
      return null;
    }
    return getLink(getFirstPLI(link, fNode) - 1); //before(pos).element();
  }

  public BLink after(BLink link, BNode fNode) {
    if (link == endLink()) {
      return null;
    }
    return getLink(getFirstPLI(link, fNode) + 1); //(CLink) path_.after(pos).element();	
  }

  // link addition methods
  public void addDirectedFirstLink(BLink link, BNode fNode) {
    firstFNode_ = fNode;
    addLink(link);
  }

  public boolean addLink(BLink link) {
    return addLink(link, true);
  }

  public boolean addLink(BLink link, boolean tryAdj) {
    if (path_.isEmpty()) {
      addFirst(link);
      return true;
    }

    //Edge newEdge = link.getEdge();

    if (isAdjacentToEnd(link)) {
      //System.out.println("add end");    
      addToEnd(link);
    } else if (isAdjacentToStart(link)) {
      //System.out.println("add start");
      addToStart(link);
    } else {
      Iterator<BLink> adjPath = null;
      //if(tryAdj) adjPath = addNonAdjacent(link);
      Set<BNode> nodes = new HashSet<BNode>();
      nodes.add(startNode());
      nodes.add(endNode());
      if (tryAdj) {
        adjPath = baseNet_.findLinksToNodeSet(link, nodes);
      }
      if (adjPath != null) {
        while (adjPath.hasNext()) {
          System.out.println("adding adjacent");
          BLink alink = adjPath.next();
          addLink(alink, false);
        }
      } else {
        //if(guiOA_ != null) guiOA_.msg("Non-adjacent");
        return false;
      }
    }

    return true;
  }
  
  public boolean addToStart(BLink link) {
    if (path_.isEmpty()) {
      addFirst(link);
      return true;
    }
    if (path_.size() == 1) { // adding second
      if (firstFNode_ != link.getFNode() && firstFNode_ != link.getTNode()) {
        firstFNode_ = baseNet_.opposite(firstFNode_, path_.get(0));
      }
    }
    if (link.getFNode() != startNode() && link.getTNode() != startNode()) {
      return false;
    }

    path_.add(0, link);
    firstFNode_ = baseNet_.opposite(firstFNode_, link);
    //System.out.println("new ffn "+firstFNode_.getID());
    link.registerPath(this);
    return true;
  }

  public boolean addToEnd(BLink link) {
    if (path_.isEmpty()) {
      addFirst(link);
      return true;
    }
    if (path_.size() == 1) { // adding second
      if (firstFNode_ == link.getFNode() || firstFNode_ == link.getTNode()) {
        firstFNode_ = baseNet_.opposite(firstFNode_, path_.get(0));
      }
    }
    if (link.getFNode() != endNode() && link.getTNode() != endNode()) {
      return false;
    }
    path_.add(path_.size(), link);
    link.registerPath(this);
    return true;
  }

  protected void addFirst(BLink link) {
    path_.add(link);
    if(firstFNode_ == null) firstFNode_ = link.getFNode();
    link.registerPath(this);
  }

  // link removal methods
  public int removeLink(BLink link) {

    if (path_.size() == 0) {
      return 1;
    }

    if (path_.size() == 1) {
      if (link == path_.get(0)) {
        clear();
        return 0;
      }
      return 1;
    }

    guiOA_.msg("rem");
    if (link == endLink()) {
      return removeLastLink();
    } else if (link == startLink()) {
      return removeFirstLink();
    } else {
      return 1;
    }
  }

  public int removeLastLink() {
    endLink().unregisterPath(this);
    path_.remove(path_.size() - 1);
    return 0;
  }

  public int removeFirstLink() {
    BLink fLink = startLink();
    fLink.unregisterPath(this);
    firstFNode_ = baseNet_.opposite(firstFNode_, fLink);
    path_.remove(0);
    return 0;
  }

  // adjacency test methods
  public boolean isAdjacentToStart(BLink link) {
    if (path_.size() == 1) {
      return (link.getFNode() == firstFNode_ || link.getTNode() == firstFNode_);
    }

    //Edge edge= link.getEdge();
    //System.out.println("checking adjacency to start");
    //Edge startEdge = startLink().getEdge(); 

    // handle partial street special case
    if (link.getClassType() == BLink.CLASS_STREETPARTIAL && link.getID() == startLink().getID()) {
      return true;
    }

    return baseNet_.areIncident(startNode(), link);
  }

  public boolean isAdjacentToEnd(BLink link) {
    if (path_.size() == 1) {
      BNode firstTNode = baseNet_.opposite(firstFNode_, path_.get(0));
      return (link.getFNode() == firstTNode || link.getTNode() == firstTNode);
    }

    //Edge edge= link.getEdge();	
    //Edge endEdge = endLink().getEdge(); 

    // handle partial street special case
    if (link.getClassType() == BLink.CLASS_STREETPARTIAL && link.getID() == endLink().getID()) {
      return true;
    }

    return baseNet_.areIncident(endNode(), link);
  }

  public boolean isAdjacent(BLink link) {
    return (isAdjacentToStart(link) || isAdjacentToEnd(link));
  }

  //// debug, etc.
  private void printIDs() {
    Iterator<BLink> links = getLinks();
    while (links.hasNext()) {
      System.out.println(" - " + links.next().getID());
    }
  }

  /**
   * Counts the number of distinct times a node is "visited" by the target
   * path. Typically 1 for a simple path; can be higher if the path crosses or
   * loops back on itself. (Or 0 if the node is not on the path at all.)
   * 
   * @param node a node along the target path
   * @return number of times node is visited
   */
  public int getNodeVisitationCount(BNode node) {
    int count = 0;
    
    for(Iterator<BNode> nodes = getNodes(); nodes.hasNext(); )
      if(nodes.next() == node) count++;

    return count;
  }
}





























